// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/service/async_pixel_transfer_manager_idle.h"
#include "gpu/command_buffer/service/async_pixel_transfer_delegate.h"

#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/memory/weak_ptr.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_synthetic_delay.h"
#include "ui/gl/scoped_binders.h"

namespace gpu {

namespace {

    static uint64 g_next_pixel_transfer_state_id = 1;

    void PerformNotifyCompletion(
        AsyncMemoryParams mem_params,
        scoped_refptr<AsyncPixelTransferCompletionObserver> observer)
    {
        TRACE_EVENT0("gpu", "PerformNotifyCompletion");
        observer->DidComplete(mem_params);
    }

} // namespace

// Class which handles async pixel transfers in a platform
// independent way.
class AsyncPixelTransferDelegateIdle
    : public AsyncPixelTransferDelegate,
      public base::SupportsWeakPtr<AsyncPixelTransferDelegateIdle> {
public:
    typedef base::Callback<GLuint()> TextureIdCallback;
    AsyncPixelTransferDelegateIdle(
        AsyncPixelTransferManagerIdle::SharedState* state,
        const TextureIdCallback& texture_id_callback,
        const AsyncTexImage2DParams& define_params);
    ~AsyncPixelTransferDelegateIdle() override;

    // Implement AsyncPixelTransferDelegate:
    void AsyncTexImage2D(const AsyncTexImage2DParams& tex_params,
        const AsyncMemoryParams& mem_params,
        const base::Closure& bind_callback) override;
    void AsyncTexSubImage2D(const AsyncTexSubImage2DParams& tex_params,
        const AsyncMemoryParams& mem_params) override;
    bool TransferIsInProgress() override;
    void WaitForTransferCompletion() override;

private:
    void PerformAsyncTexImage2D(AsyncTexImage2DParams tex_params,
        AsyncMemoryParams mem_params,
        const base::Closure& bind_callback);
    void PerformAsyncTexSubImage2D(AsyncTexSubImage2DParams tex_params,
        AsyncMemoryParams mem_params);

    uint64 id_;
    TextureIdCallback texture_id_callback_;
    bool transfer_in_progress_;
    AsyncTexImage2DParams define_params_;

    // Safe to hold a raw pointer because SharedState is owned by the Manager
    // which owns the Delegate.
    AsyncPixelTransferManagerIdle::SharedState* shared_state_;

    DISALLOW_COPY_AND_ASSIGN(AsyncPixelTransferDelegateIdle);
};

AsyncPixelTransferDelegateIdle::AsyncPixelTransferDelegateIdle(
    AsyncPixelTransferManagerIdle::SharedState* shared_state,
    const TextureIdCallback& texture_id_callback,
    const AsyncTexImage2DParams& define_params)
    : id_(g_next_pixel_transfer_state_id++)
    , texture_id_callback_(texture_id_callback)
    , transfer_in_progress_(false)
    , define_params_(define_params)
    , shared_state_(shared_state)
{
}

AsyncPixelTransferDelegateIdle::~AsyncPixelTransferDelegateIdle() { }

void AsyncPixelTransferDelegateIdle::AsyncTexImage2D(
    const AsyncTexImage2DParams& tex_params,
    const AsyncMemoryParams& mem_params,
    const base::Closure& bind_callback)
{
    TRACE_EVENT_SYNTHETIC_DELAY_BEGIN("gpu.AsyncTexImage");
    DCHECK_EQ(static_cast<GLenum>(GL_TEXTURE_2D), tex_params.target);

    shared_state_->tasks.push_back(AsyncPixelTransferManagerIdle::Task(
        id_,
        this,
        base::Bind(&AsyncPixelTransferDelegateIdle::PerformAsyncTexImage2D,
            AsWeakPtr(),
            tex_params,
            mem_params,
            bind_callback)));

    transfer_in_progress_ = true;
}

void AsyncPixelTransferDelegateIdle::AsyncTexSubImage2D(
    const AsyncTexSubImage2DParams& tex_params,
    const AsyncMemoryParams& mem_params)
{
    TRACE_EVENT_SYNTHETIC_DELAY_BEGIN("gpu.AsyncTexImage");
    DCHECK_EQ(static_cast<GLenum>(GL_TEXTURE_2D), tex_params.target);

    shared_state_->tasks.push_back(AsyncPixelTransferManagerIdle::Task(
        id_,
        this,
        base::Bind(&AsyncPixelTransferDelegateIdle::PerformAsyncTexSubImage2D,
            AsWeakPtr(),
            tex_params,
            mem_params)));

    transfer_in_progress_ = true;
}

bool AsyncPixelTransferDelegateIdle::TransferIsInProgress()
{
    return transfer_in_progress_;
}

void AsyncPixelTransferDelegateIdle::WaitForTransferCompletion()
{
    for (std::list<AsyncPixelTransferManagerIdle::Task>::iterator iter = shared_state_->tasks.begin();
         iter != shared_state_->tasks.end();
         ++iter) {
        if (iter->transfer_id != id_)
            continue;

        (*iter).task.Run();
        shared_state_->tasks.erase(iter);
        break;
    }

    shared_state_->ProcessNotificationTasks();
}

void AsyncPixelTransferDelegateIdle::PerformAsyncTexImage2D(
    AsyncTexImage2DParams tex_params,
    AsyncMemoryParams mem_params,
    const base::Closure& bind_callback)
{
    TRACE_EVENT2("gpu", "PerformAsyncTexImage2D",
        "width", tex_params.width,
        "height", tex_params.height);

    void* data = mem_params.GetDataAddress();

    base::TimeTicks begin_time(base::TimeTicks::Now());
    gfx::ScopedTextureBinder texture_binder(tex_params.target,
        texture_id_callback_.Run());

    {
        TRACE_EVENT0("gpu", "glTexImage2D");
        glTexImage2D(
            tex_params.target,
            tex_params.level,
            tex_params.internal_format,
            tex_params.width,
            tex_params.height,
            tex_params.border,
            tex_params.format,
            tex_params.type,
            data);
    }

    TRACE_EVENT_SYNTHETIC_DELAY_END("gpu.AsyncTexImage");
    transfer_in_progress_ = false;
    shared_state_->texture_upload_count++;
    shared_state_->total_texture_upload_time += base::TimeTicks::Now() - begin_time;

    // The texture is already fully bound so just call it now.
    bind_callback.Run();
}

void AsyncPixelTransferDelegateIdle::PerformAsyncTexSubImage2D(
    AsyncTexSubImage2DParams tex_params,
    AsyncMemoryParams mem_params)
{
    TRACE_EVENT2("gpu", "PerformAsyncTexSubImage2D",
        "width", tex_params.width,
        "height", tex_params.height);

    void* data = mem_params.GetDataAddress();

    base::TimeTicks begin_time(base::TimeTicks::Now());
    gfx::ScopedTextureBinder texture_binder(tex_params.target,
        texture_id_callback_.Run());

    if (shared_state_->use_teximage2d_over_texsubimage2d && tex_params.xoffset == 0 && tex_params.yoffset == 0 && tex_params.target == define_params_.target && tex_params.level == define_params_.level && tex_params.width == define_params_.width && tex_params.height == define_params_.height) {
        TRACE_EVENT0("gpu", "glTexImage2D");
        glTexImage2D(
            define_params_.target,
            define_params_.level,
            define_params_.internal_format,
            define_params_.width,
            define_params_.height,
            define_params_.border,
            tex_params.format,
            tex_params.type,
            data);
    } else {
        TRACE_EVENT0("gpu", "glTexSubImage2D");
        glTexSubImage2D(
            tex_params.target,
            tex_params.level,
            tex_params.xoffset,
            tex_params.yoffset,
            tex_params.width,
            tex_params.height,
            tex_params.format,
            tex_params.type,
            data);
    }

    TRACE_EVENT_SYNTHETIC_DELAY_END("gpu.AsyncTexImage");
    transfer_in_progress_ = false;
    shared_state_->texture_upload_count++;
    shared_state_->total_texture_upload_time += base::TimeTicks::Now() - begin_time;
}

AsyncPixelTransferManagerIdle::Task::Task(
    uint64 transfer_id,
    AsyncPixelTransferDelegate* delegate,
    const base::Closure& task)
    : transfer_id(transfer_id)
    , delegate(delegate)
    , task(task)
{
}

AsyncPixelTransferManagerIdle::Task::~Task() { }

AsyncPixelTransferManagerIdle::SharedState::SharedState(
    bool use_teximage2d_over_texsubimage2d)
    : use_teximage2d_over_texsubimage2d(use_teximage2d_over_texsubimage2d)
    , texture_upload_count(0)
{
}

AsyncPixelTransferManagerIdle::SharedState::~SharedState() { }

void AsyncPixelTransferManagerIdle::SharedState::ProcessNotificationTasks()
{
    while (!tasks.empty()) {
        // Stop when we reach a pixel transfer task.
        if (tasks.front().transfer_id)
            return;

        tasks.front().task.Run();
        tasks.pop_front();
    }
}

AsyncPixelTransferManagerIdle::AsyncPixelTransferManagerIdle(
    bool use_teximage2d_over_texsubimage2d)
    : shared_state_(use_teximage2d_over_texsubimage2d)
{
}

AsyncPixelTransferManagerIdle::~AsyncPixelTransferManagerIdle() { }

void AsyncPixelTransferManagerIdle::BindCompletedAsyncTransfers()
{
    // Everything is already bound.
}

void AsyncPixelTransferManagerIdle::AsyncNotifyCompletion(
    const AsyncMemoryParams& mem_params,
    AsyncPixelTransferCompletionObserver* observer)
{
    if (shared_state_.tasks.empty()) {
        observer->DidComplete(mem_params);
        return;
    }

    shared_state_.tasks.push_back(
        Task(0, // 0 transfer_id for notification tasks.
            NULL,
            base::Bind(
                &PerformNotifyCompletion,
                mem_params,
                make_scoped_refptr(observer))));
}

uint32 AsyncPixelTransferManagerIdle::GetTextureUploadCount()
{
    return shared_state_.texture_upload_count;
}

base::TimeDelta AsyncPixelTransferManagerIdle::GetTotalTextureUploadTime()
{
    return shared_state_.total_texture_upload_time;
}

void AsyncPixelTransferManagerIdle::ProcessMorePendingTransfers()
{
    if (shared_state_.tasks.empty())
        return;

    // First task should always be a pixel transfer task.
    DCHECK(shared_state_.tasks.front().transfer_id);
    shared_state_.tasks.front().task.Run();
    shared_state_.tasks.pop_front();

    shared_state_.ProcessNotificationTasks();
}

bool AsyncPixelTransferManagerIdle::NeedsProcessMorePendingTransfers()
{
    return !shared_state_.tasks.empty();
}

void AsyncPixelTransferManagerIdle::WaitAllAsyncTexImage2D()
{
    if (shared_state_.tasks.empty())
        return;

    const Task& task = shared_state_.tasks.back();
    if (task.delegate)
        task.delegate->WaitForTransferCompletion();
}

AsyncPixelTransferDelegate*
AsyncPixelTransferManagerIdle::CreatePixelTransferDelegateImpl(
    gles2::TextureRef* ref,
    const AsyncTexImage2DParams& define_params)
{
    return new AsyncPixelTransferDelegateIdle(
        &shared_state_,
        // Not directly passing texture_ref->service_id here because it can change
        // if avoid_egl_image_target_texture_reuse workaround is in effect.
        // Unretained is safe because AsyncPixelTransferManager observes
        // TextureRef destruction and destroys the delegate before TextureRef
        // is destroyed.
        base::Bind(&gles2::TextureRef::service_id, base::Unretained(ref)),
        define_params);
}

} // namespace gpu
